/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package dbsucxentW.constructW;

import dnbW.dbW.DataSource;
import dnbW.dbW.Document;
import dnbW.entityW.Path;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;
import javax.swing.tree.DefaultMutableTreeNode;

/**
 *
 * @author truongbaquan
 */
public class TreeReconstructor {
    ResultSet resultSet;
    DataSource dataSource;
    Map<Integer, Path> paths = new HashMap<Integer, Path>();
    String curElement = null;
    Vector attribute = new Vector();
    long curAncestorDeweyGroup, oldAncestorDeweyGroup;
    FileOutputStream fileOutputStream;
    StringBuilder stringResult;
    long currentRow = 0;
    long endRow = 0;
    long totalRow = 0;
    boolean overflow = false;

    private final String ROOTNAME = "results";
    int curLevel = -1;
    private DefaultMutableTreeNode[] nodes = new DefaultMutableTreeNode[20];

    public TreeReconstructor(ResultSet resultSet, DataSource dataSource,
            long current, long end) {
        this.resultSet = resultSet;
        this.dataSource = dataSource;
        currentRow = current;
        endRow = end;

        retrievePaths();
    }

    // reconstruct and output the result as a tree
    public DefaultMutableTreeNode reconstructTree() throws FileNotFoundException, SQLException, IOException {
        DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(ROOTNAME);
        nodes[++curLevel] = rootNode;
        reconstruct();
        return rootNode;
    }

    // start the reconstruction process
    public void reconstruct() throws FileNotFoundException, SQLException, IOException {
        long maxMem = (Runtime.getRuntime().maxMemory() / 10) * 5;	// set the max memory to use before throw out of memory exception

        // obtain the total number of rows (also the estimated number of tuples in the interface)
        resultSet.last();
        totalRow = resultSet.getRow();

        if (totalRow == 0) return; // if the result set is empty, return root node with empty results

        // go to the first node to be constructed, either from row 0 or from a specify location currentRow
        if (currentRow == 0) {
            resultSet.first();
        } else {
            resultSet.absolute((int) currentRow);
        }

        int pathId;
        pathId = resultSet.getInt("PathId");
        
        Document document = this.dataSource.tryGetDocument(this.resultSet.getInt("DocId"));

        // write out the paths from queryDepth to the first leaf node
        Path oldPath;
        Path path = this.getPath(pathId);
        for (int level = 1; level < path.getLevel(); level++) {
            startElement(path.getElementAtLevel(level));
        }

        String value = resultSet.getString("LeafValue");
        if (!path.isAttribute()) {
            startElement(path.getElementAtLevel(path.getLevel()));
            if (value != null) {
                writeLeaf(value.trim());
            } else {
                writeLeaf(" ");
            }
        } else {
            String attributeName = path.getElementAtLevel(path.getLevel());
            attributeName = attributeName.substring(1, attributeName.length());
            writeAttribute(attributeName, value.trim());
        }
        currentRow++;

        oldAncestorDeweyGroup = resultSet.getLong("AncestorDeweyGroup");
        oldPath = path;

        // loop through all nodes until we reach the last node
        while (resultSet.next()) {
            pathId = resultSet.getInt("PathId");
            path = this.getPath(pathId);
            curAncestorDeweyGroup = resultSet.getLong("AncestorDeweyGroup");

            // find the nearest common ancestor (if the two paths are from different documents, their NCA level is 0)
            int docId = resultSet.getInt("DocId");
            int ncaLevel;
            if (docId == document.getDocId()) { // same document          
                ncaLevel = Path.findLCALevel(path, oldPath);
                for (; ncaLevel >= 2; ncaLevel--) {
                    if (curAncestorDeweyGroup / document.getAncestorValue(ncaLevel-1) == oldAncestorDeweyGroup / document.getAncestorValue(ncaLevel-1)) {
                        break;
                    }
                }
            }
            else { // different documents
                document = this.dataSource.tryGetDocument(docId);
                ncaLevel = 0;
            }

            int level = oldPath.getLevel();
            if (oldPath.isAttribute()) {
                level--;
            }
            // close path till the common ancestor
            for (; level > ncaLevel; level--) {
                endElement();
            }

            value = resultSet.getString("LeafValue");
            level = ncaLevel + 1;
            // open path from common ancestor to second node
            for (; level < path.getLevel(); level++) {
                startElement(path.getElementAtLevel(level));
            }
            // write out the leaf value
            if (!path.isAttribute()) {
                // if level is increased and now equal to path.level
                // (level may alternatively be equal to path.level+1 initially)
                if (level == path.getLevel()) {
                    startElement(path.getElementAtLevel(level));
                }
                if (value != null) {
                    writeLeaf(value.trim());
                } else {
                    writeLeaf(" ");
                }
            } else {
                String attributeName = path.getElementAtLevel(level);
                attributeName = attributeName.substring(1, attributeName.length());
                writeAttribute(attributeName, value.trim());
            }

            // replace first node with second node and move on to the next node
            oldAncestorDeweyGroup = curAncestorDeweyGroup;
            oldPath = path;

            currentRow++;
            // if used memory exceed the quota set, stop reconstruction (need to leave some memory for GUI display)
            if (endRow == 0 && (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) > maxMem) {
                //System.out.println("Memory OverFlow!");
                overflow = true;
                break;
            // reconstruct within the range of rows specified
            } else if (endRow != 0 && currentRow == endRow) {
                overflow = true;
                break;
            }
        }

        if (overflow) {
            resultSet.next();
        }
        if (!oldPath.isAttribute()) {
            for (int i = oldPath.getLevel() - 1; i >= 1; i--) {
                endElement();
            }
        } else {
            for (int i = oldPath.getLevel() - 2; i >= 1; i--) {
                endElement();
            }
        }
        System.out.println("Count = " + currentRow);
    }

    // declare a start element, the writing of start element is delay by 1
    private void startElement(String element) throws IOException {
        DefaultMutableTreeNode node = new DefaultMutableTreeNode(element);
        this.getCurNode().add(node);
        this.nodes[++curLevel] = node;
    }

    // declare an end element and write out this element immediately
    private void endElement() throws IOException {
        curLevel--;
    }

    private void writeAttribute(String attribute, String value) {
        DefaultMutableTreeNode node = new DefaultMutableTreeNode("@" + attribute);
        if (value != null && !value.trim().equals(""))
            node.add(new DefaultMutableTreeNode(value));
        this.getCurNode().add(node);
    }

    // write the leaf value
    private void writeLeaf(String leaf) throws IOException {
        if (leaf != null && !"".equals(leaf.trim())) {
            DefaultMutableTreeNode node = new DefaultMutableTreeNode(leaf);
            this.getCurNode().add(node);
        }
    }

    private DefaultMutableTreeNode getCurNode() {
        return this.nodes[curLevel];
    }

    // get all the paths and return it in an array of string
    private void retrievePaths() {
        try {
            this.dataSource.getDatabase().getDbConnection().commit();
            Statement statement = this.dataSource.getDatabase().getDbConnection().createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
                    ResultSet.CONCUR_READ_ONLY); 	// ms sql server 2005

            ResultSet sqlResults = statement.executeQuery(
                    "SELECT PathId, PathExp FROM " + this.dataSource.getName() + "_Path ORDER BY PathID");

            while (sqlResults.next()) {
                int pathId = sqlResults.getInt("PathId");
                String pathExp = sqlResults.getString("PathExp");
                this.paths.put(pathId, Path.buildPathFromSqlPathExp(dataSource, pathExp));
            }
            sqlResults.close();
            statement.close();
        }
        catch (SQLException ex) {
            System.err.println(ex.getMessage());
        }
    }

    private Path getPath(int pathId) {
        return this.paths.get(pathId);
    }

    public long getRow() {
        return currentRow;
    }

    public long getTotalRows() {
        return totalRow;
    }

    public boolean isOverflow() {
        return overflow;
    }
}
